<?php
namespace Dbak\lib;
/**
 * Class phpMysqlBackup
 * 用于数据备份
 * 这里mysql备份分卷，分别有导入和导出功能
 * 操作：create、insert
 * 备份文件名格式:20151218161101__0.gt或
 * 将建表语句和插入语句分开
 */
class PhpMysqlBackup{

    //分卷大小
    private $blockSize = '';
    //当前文件名
    private $currentFile = '';
    //当前句柄
    private $fp;
    private  $index = 0;
    //当前文件是否打开
    private $fileIsOpen = true;
    //文件名游标，用于备份时文件名连续
    private $file_fetch = '_';
    //文件后缀名
    private  $file_type = 'gt';
    private $units = array(' B', ' KB', ' MB', ' GB', ' TB');

    /**
     * @var array
     * 数据配置
     */
    private $option = array(
        'host' => 'localhost',
        'username' => 'root',
        'password' => 'root',
        'charset' => 'utf8',
        'backupFloder' => '',
        'errorLog' => '',
        'blockSize' => 2048,
    );

     /**
      * 这里规定：传入的整个数据库，而不是选定的表
      */

    /**
     * @param $host
     * @param $usr
     * @param $pw
     * @param $charset
     * @param $db
     * @param int $blockSize，默认的分卷大小是1024KB，不知道程序哪里有问题，实际压缩大小只有块大小的一半
     */
    public function init($option){
        //过滤输入
        foreach($option as $k => $v){
            $this->option[$k] = $this->filterinput($option[$k]);
        }
        //配置基础数据
        if(!isset($this->option['blockSize'])){
            $this->option['blockSize'] = 2048;
        }
        $this->blockSize = $option['blockSize'];
        if(!isset($this->option['backupFloder']) || $this->option['backupFloder'] == ''){
            $this->option['backupFloder'] = './db_backup/'.date('YmdHis').'/';
        }
        $this->option['errorLog'] = './backup_error_log.log';
        if(!$this->checkSavePath($this->option['backupFloder'])){
            //传入路径不存在
            mkdir($this->option['backupFloder'], 0777, true);
        }
        //计算文件名开始游标
        $this->getExistFileIndex();
        //开始执行
        $this->setPar($this->option['host'], $this->option['username'], $this->option['password']
            , $this->option['charset'], $this->option['database'], $this->option['blockSize']);
    }

    /**
     * @param $host
     * @param $usr
     * @param $pw
     * @param $charset
     * @param $db 数据库，可以是单个，也可以是一个数组
     * 启动唯一入口方法
     */
    private function setPar($host, $usr, $pw, $charset, $db, $blockSize){
        if(!isset($host)){
            $host = $this->option['host'];
        }
        if(!isset($usr)){
            $usr = $this->option['username'];
        }
        if(!isset($pw)){
            $pw = $this->option['password'];
        }
        if(!isset($charset)){
            $charset = $this->option['charset'];
        }
        if(!isset($db)){
            echo '数据库为空，错误!!!';
            return;
        }
        $blockSize = intval($blockSize);
        //重点用于过滤$usr,$pw,$db
        $usr = $this->filterinput($usr);
        $pw = $this->filterinput($pw);
        $db = $this->filterinput($db);
        //字符串过滤
        if(is_array($db)){
            //备份多个数据库
        }else{
            //备份单个数据库
            $this->backup($host, $usr, $pw, $charset, $db,$blockSize);
        }
    }

    /**
     * @param $table
     * 建表语句的导出
     */
    private function sqldumptable($table) {
        @mysql_query("SET SQL_QUOTE_SHOW_CREATE = 1");
        $query=mysql_query("SHOW CREATE TABLE `$table`");
        $row=mysql_fetch_row($query);
        $drop = 'DROP TABLE IF EXISTS `'.$table.'`;';
        $sql = mysql_real_escape_string($row[1]);
        $sql=str_replace("\n","\n",str_replace("\"","\\\"",$row[1]));
        $dumpstring=$drop."\n".$sql.";\n";
        $this->writeFile( "-- \n-- {$table}\n-- \n");
        $this->writeFile($dumpstring);
        mysql_free_result($query);
    }

        /**
     * @param $database
     * @return array
     * 列出指定数据库中所有的表
     */
    private function list_tables($database){
        $rs = mysql_list_tables($database);
        $tables = array();
        while ($row = mysql_fetch_row($rs)) {
            $tables[] = $row[0];
            foreach($row as $k => $v){
                //echo $k;
            }
        }
        mysql_free_result($rs);
        return $tables;
    }

    /**
     * @param $table
     * @param null $fp
     * 获取某张表中的数据，并写入指定的文件中
     */
    private function dump_table($table, $db){
        $need_close = false;
        if (is_null($this->fp)) {
            $need_close = true;
        }
        $rs = mysql_query("SELECT * FROM `{$table}`");
        //sql拼接语句，用于检测长度是否超过块大小
        $tempSql = '';
        $temp = '';
        while ($row = mysql_fetch_row($rs)) {
//            fwrite($this->fp, $this->get_insert_sql( $table, $row));
            //得到单条insert语句
            $temp = $this->get_insert_sql( $table, $row);
            $this->tempSql .= $temp;
            $this->checkCurrentBackupFileSize($temp);
        }
        mysql_free_result($rs);
    }

    /**
     * @param $table
     * @param $row
     * @return string
     * 得到表中所有的INSERT语句
     * 写入文件在这里进行
     */
    private function get_insert_sql($table, $row){
        $sql = "INSERT INTO `{$table}` VALUES (";
        $values = array();
        foreach ($row as $value) {
            $values[] = "'" . mysql_real_escape_string($value) . "'";
        }
        $sql .= implode(', ', $values) . ");\n";
        return $sql;
    }

    /**
     * 计算文件名游标
     * 游标为__
     */
    private function getExistFileIndex(){
        $files = $this->searchFile($this->option['backupFloder']);
        if(count($files) > 0){
            //获取最后一个文件
            $lastFile = $files[count($files)-1];
            $index = substr($lastFile, strrpos($lastFile,$this->file_fetch)+strlen($this->file_fetch));
            $this->index = intval(substr($index,0,  strrpos($index,'.')))+1;
        }
        //初始化第一个文件名
        $this->currentFile = date('YmdHis').'__'.$this->index.'.'.$this->file_type;
    }

    /************************************-- 往下为基础函数 --******************************************************************************/

    public function backup($host, $usr, $pw, $charset, $db, $blockSize){
        $this->createConnection($host, $usr, $pw, $charset, $db);
        $this->openCurrentBackupFile();
        $this->beforeHock();
        $this->doBackup($db, $blockSize);
        $this->behindHock();
        $this->closeCurrentBackupFile();
        $this->closeConnectiong();
    }

    /**
     * 真正起始执行的函数
     */
    private function doBackup($db, $blockSize){
        //设置要导出的表
        $tables = $this->list_tables($db);
        foreach ($tables as $table) {
            $this->sqldumptable($table);
            $this->dump_table($table, $db);
        }
    }

    /**
     * 前钩子
     */
    public function beforeHock(){

    }

    /**
     * 后钩子
     */
    public function behindHock(){

    }

    /**
     * @param $host
     * @param $usr
     * @param $pw
     * @param $charset
     * @param $db
     * 创建一个链接
     */
    private function createConnection($host, $usr, $pw, $charset, $db){
        $con = mysql_connect($host, $usr, $pw);
        mysql_select_db($db, $con);
        mysql_query("SET NAMES ".$charset);
    }

    private function closeConnectiong(){
        mysql_close();
    }

    /**
     * @param $input
     *过滤输入字符串，防止攻击
     */
    private function filterinput($input){
        $input = mysql_real_escape_string($input);
        return $input;
    }

    /**
     * @param $size
     * @return array
     * 长度换算，默认分卷为1M大小
     * 返回KB单位
     */
    function formatBytes($size) {
//        for ($i = 0; $size >= 1024 && $i < 4; $i++)
//            $size /= 1024;
//        return round($size, 2).$units[$i];
        //只用换算到KB就可以了
//        if($size<1024){
//            return array('size'=>1, 'unit'=>$this->units[1]);
//        }
        $size /= 1024;
        return array('size'=>round($size, 2), 'unit'=>$this->units[1]);
    }

    /**
     * @param $path
     * @return bool
     * 检查传入是否正确
     */
    private function checkSavePath($path){
        return is_dir($path) ? true : false;
    }

    private function isFile($file){
        return is_dir($file) ? false : true;
    }

    /**
     * @param $errormsg
     * 写入错误日志
     */
    private function writeErrorLog($errormsg){
        if($this->isFile($this->option['errorLog'])){
            $fp = fopen($this->option['errorLog'], 'w');
            $errormsg = 'create at '.date('Y-m-d H:i:s').'\t'.$errormsg.'\n';
            fwrite($fp, $errormsg);
            fclose($fp);
        }
    }

    /**
     * @param $dir
     * @return array
     * 扫描文件
     * 扫描游标文件，游标为__2
     */
    private function searchFile($dir){
        $file = scandir($dir);
        $fileArray = array();
        foreach($file as $k=> $v){
            if ($v != "." && $v != ".."&&strrpos($v,".")&&strrpos($v,$this->$file_fetch)) {
                $fileArray[] = $v;
            }
        }
        return $fileArray;
    }

    /**
     * 当前备份文件操作
     */
    private function openCurrentBackupFile(){
        $this->closeCurrentBackupFile();
        $this->fp = fopen($this->option['backupFloder'].$this->currentFile, 'w');
        $this->fileIsOpen = true;
    }

    public function closeCurrentBackupFile(){
        fclose($this->fp);
        $this->fileIsOpen = false;
    }

    public function writeFile($input){
        if(!$this->fileIsOpen){
            //$this->openCurrentBackupFile();
        }
        fwrite($this->fp, $input);
        //$this->closeCurrentBackupFile();
    }

    /**
     * 容量满后，创建一个新的备份文件
     */
    private function createNewBackupFile(){
        //为避免写入文件错误，先关闭上一个文件，再开启新文件
        $this->closeCurrentBackupFile();
        $this->index++;
        $this->currentFile = date('YmdHis').'__'.$this->index.'.'.$this->file_type;
        $this->openCurrentBackupFile();
    }

    /**
     * @param $input
     * 检查备份文件大小
     */
    private function checkCurrentBackupFileSize($input){
        $bytes = strlen($this->tempSql);
        if($this->formatBytes(strlen($this->tempSql))['size'] >= $this->option['blockSize']){
            $this->writeFile($this->tempSql);
            $this->createNewBackupFile();
            $this->tempSql = '';
        }
        ob_flush();
        flush();
    }

    /**
     * 这个方法单纯用于检测字符串长度，不带有其他操作
     * 检测是否超过大小
     */
    private function  isOverBlockSize($input){
        if($this->formatBytes(strlen($input))['size'] >= ($this->blockSize-6)) {
            return true;
        }else{
            return false;
        }
    }
}